package com.xiannimei.util.httpUtils;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.httpclient.cookie.CookiePolicy;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.params.HttpMethodParams;
import org.apache.log4j.Logger;

public class HttpUtil{
	private final Logger logger = Logger.getLogger(HttpUtil.class);

	private String charset = "UTF-8";

	private int timeOut = 0;

	private int soTimeout = 0;

	protected HttpClient httpClient;

	public HttpUtil(){
		this(2000, 5000);
	}

	public HttpUtil(int timeOut, int soTimeout){
		this.timeOut = timeOut;
		this.soTimeout = soTimeout;
		initHttpClient();
	}

	public String getCharset(){
		return charset;
	}

	public void setCharset(String charset){
		this.charset = charset;
	}

	public int getTimeOut(){
		return timeOut;
	}

	public void setTimeOut(int timeOut){
		this.timeOut = timeOut;
	}

	public int getSoTimeout(){
		return soTimeout;
	}

	public void setSoTimeout(int soTimeout){
		this.soTimeout = soTimeout;
	}

	private HttpClient initHttpClient(){
		httpClient = new HttpClient();
		httpClient.getParams().setParameter("http.protocol.content-charset", charset);
		httpClient.getHttpConnectionManager().getParams().setConnectionTimeout(timeOut);
		httpClient.getHttpConnectionManager().getParams().setSoTimeout(soTimeout);
		httpClient.getParams().setParameter(HttpMethodParams.USER_AGENT, "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.1.2) Gecko/20090729 Firefox/3.5.2");
		return httpClient;
	}

	protected GetMethod initGetMethod(HttpRequest req){
		return initGetMethod(req, null);
	}

	protected GetMethod initGetMethod(HttpRequest req, List<Header> headers){
		GetMethod getMethod = new GetMethod(req.getUrl());
		getMethod.getParams().setParameter("http.protocol.cookie-policy", CookiePolicy.BROWSER_COMPATIBILITY);
		Header hConnection = new Header();
		hConnection.setName("Connection");
		hConnection.setValue("Keep-Alive");
		getMethod.addRequestHeader(hConnection);
		getMethod.setRequestHeader("User-Agent", "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.1.2) Gecko/20090729 Firefox/3.5.2");
		getMethod.setRequestHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");

		String host = req.getHost();
		if (host != null && !"".equals(host)) {
			Header hHost = new Header();
			hHost.setName("Host");
			hHost.setValue(host);
			getMethod.addRequestHeader(hHost);
			Header hReferer = new Header();
			hReferer.setName("Referer");
			hReferer.setValue("http://" + host + "/");
			getMethod.addRequestHeader(hReferer);
		}

		String cookies = req.getCookieStr();
		if (cookies != null && !"".equals(cookies)) {
			Header hCookie = new Header();
			hCookie.setName("Cookie");
			hCookie.setValue(cookies);
			getMethod.addRequestHeader(hCookie);
		}

		if (headers != null) {
			for (Header header : headers) {
				getMethod.addRequestHeader(header);
			}
		}

		String params = req.getParams();
		if (params != null && !"".equals(params)) {
			getMethod.setQueryString(params);
		}

		return getMethod;
	}

	protected PostMethod initPostMethod(HttpRequest req){
		return initPostMethod(req, null);
	}

	protected PostMethod initPostMethod(HttpRequest req, List<Header> headers){
		PostMethod postMethod = new PostMethod(req.getUrl());
		postMethod.getParams().setParameter("http.protocol.cookie-policy", CookiePolicy.BROWSER_COMPATIBILITY);

		Header hConnection = new Header();
		hConnection.setName("Connection");
		hConnection.setValue("Keep-Alive");
		postMethod.addRequestHeader(hConnection);
		postMethod.setRequestHeader("User-Agent", "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.1.2) Gecko/20090729 Firefox/3.5.2");
		postMethod.setRequestHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");

		String host = req.getHost();
		if (host != null && !"".equals(host)) {
			Header hHost = new Header();
			hHost.setName("Host");
			hHost.setValue(host);
			postMethod.addRequestHeader(hHost);
			Header hReferer = new Header();
			hReferer.setName("Referer");
			hReferer.setValue("http://" + host + "/");
			postMethod.addRequestHeader(hReferer);
		}

		String cookies = req.getCookieStr();
		if (cookies != null && !"".equals(cookies)) {
			Header hCookie = new Header();
			hCookie.setName("Cookie");
			hCookie.setValue(cookies);
			postMethod.addRequestHeader(hCookie);
		}

		if (headers != null) {
			for (Header header : headers) {
				postMethod.addRequestHeader(header);
			}
		}

		String params = req.getParams();
		if (params != null && !"".equals(params)) {
			String[] _params = params.split("&");
			NameValuePair[] nvps = new NameValuePair[_params.length];
			for (int i = 0; i < _params.length; i++) {
				String _p = _params[i];
				int j = _p.indexOf("=");
				nvps[i] = new NameValuePair(_p.substring(0, j), (_p.length() > j + 1) ? _p.substring(j + 1) : "");
			}
			postMethod.addParameters(nvps);
		}

		return postMethod;
	}

	protected Map<String, String> getCookiesByResponseHeaders(Header[] headers){
		Map<String, String> m = new HashMap<String, String>();
		for (Header header : headers) {
			if ("Set-Cookie".equals(header.getName())) {
				for (String s : header.getValue().split(";")) {
					s = s.trim();
					int i = s.indexOf("=");
					if (i != -1) {
						m.put(s.substring(0, i), (s.length() > i + 1) ? s.substring(i + 1) : "");
					}
				}
			}
		}
		return m;
	}

	protected Map<String, String> getHeadersByResponseHeaders(Header[] headers){
		Map<String, String> m = new HashMap<String, String>();
		for (Header header : headers) {
			if (!"Set-Cookie".equals(header.getName())) {
				m.put(header.getName(), header.getValue());
			}
		}
		return m;
	}

	public HttpResponse request(HttpRequest req) throws HttpException, IOException{
		return request(req, null);
	}

	public HttpResponse request(HttpRequest req, List<Header> headers) throws HttpException, IOException{
		return request(req, headers, HttpStatus.SC_OK);
	}

	public HttpResponse request(HttpRequest req, int successStatus) throws HttpException, IOException{
		return request(req, null, successStatus);
	}

	public HttpResponse request(HttpRequest req, List<Header> headers, int successStatus) throws HttpException, IOException{
		return request(req, headers, successStatus, false);
	}

	public HttpResponse request(HttpRequest req, List<Header> headers, int successStatus, boolean isPost) throws HttpException, IOException{
		HttpMethod httpMethod = isPost ? initPostMethod(req, headers) : initGetMethod(req, headers);
		HttpResponse resp = new HttpResponse();
		try {
			int statusCode = httpClient.executeMethod(httpMethod);
			logger.debug("request statusCode:" + statusCode + "," + req.getUrl());
			resp.setStatusCode(statusCode);

			if (successStatus == -1 || statusCode == successStatus) {
				if (req.isNeedCookie()) {
					Map<String, String> _cookies = getCookiesByResponseHeaders(httpMethod.getResponseHeaders());
					resp.setCookieMap(_cookies);
				}
				if (req.isNeedHeaderMap()) {
					Map<String, String> _headers = getHeadersByResponseHeaders(httpMethod.getResponseHeaders());
					resp.setHeaderMap(_headers);
				}
				if (req.isNeedBody()) {
					String contentType = httpMethod.getResponseHeader("Content-Type").getValue();
					if (contentType != null && needWrap(contentType)) {
						resp.setBody(getResBody(httpMethod));
					} else {
						resp.setByteBody(getResByteBody(httpMethod));
					}
				}
			} else {
				throw new RuntimeException("request failed(status):" + statusCode + "/" + successStatus + "," + req.getUrl());
			}
		} finally {
			httpMethod.releaseConnection();
		}
		return resp;
	}

	public void requestNoResp(HttpRequest req) throws HttpException, IOException{
		requestNoResp(req, null);
	}

	public void requestNoResp(HttpRequest req, List<Header> headers) throws HttpException, IOException{
		PostMethod postMethod = initPostMethod(req, headers);
		try {
			int statusCode = httpClient.executeMethod(postMethod);
			logger.debug("requestNoResp statusCode:" + statusCode + "," + req.getUrl());
		} finally {
			postMethod.releaseConnection();
		}
	}

	private boolean needWrap(String contentType){
		if (contentType == null) {
			return false;
		} else if (contentType.contains("text/html") || contentType.contains("text/css") || contentType.contains("javascript")) {
			return true;
		}
		return false;
	}

	private String getResBody(HttpMethod httpMethod) throws IOException{
		StringBuilder sb = new StringBuilder(128);
		BufferedReader br = new BufferedReader(new InputStreamReader(httpMethod.getResponseBodyAsStream(), charset));
		String str = null;
		while ((str = br.readLine()) != null) {
			sb.append(str);
		}
		return sb.toString();
	}

	private byte[] getResByteBody(HttpMethod httpMethod){
		InputStream is = null;

		List<Byte> resList = null;
		try {
			resList = new ArrayList<Byte>();
			is = httpMethod.getResponseBodyAsStream();
			byte[] b = new byte[1];
			while (is.read(b) != -1) {
				resList.add(new Byte(b[0]));
				b = new byte[1];
			}
		} catch (Exception e) {
			logger.warn(e.toString());
		} finally {
			if (is != null) {
				try {
					is.close();
				} catch (IOException e) {
					logger.warn(e.toString());
				}
			}
		}

		byte[] res = new byte[resList.size()];
		for (int i = 0; i < resList.size(); i++) {
			res[i] = ((Byte) resList.get(i)).byteValue();
		}

		return res;
	}
}
